#include "minifilesystem.h"

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>

#include "hdi.h"
#include "inode.h"
#include "filehandle.h"
#include "path.h"

/*static*/ HardDriveInterface_t *hdi = NULL;
/*static*/ FileSystemHandle_t *fsh = NULL;
inodeid_t currentDirectory = 0;

/// Warn if the fsh is not initialized.
///
/// \return 0 if fsh, is initialized.
static int avoidSegfaultWithFsh(){
	if(fsh) return 0;
	fprintf(stderr,"Error : A function was called while the disk was uninitilalized.\n");
	#ifdef COMPILE_FOR_PYTHON
		fprintf(stderr,"Call format() before.\n");
	#else
		fprintf(stderr,"Call Minformat() before.\n");
	#endif
	return 1;
}

int Minformat(unsigned short int blockSize,unsigned int blockCount){
	//GLOBAL_debugPrintEnabled = false;
	if(hdi){// Already initialized.
		if(fs_deleteVirtualHDI(hdi)) return -1;
	}else{
		hdi = malloc(sizeof(HardDriveInterface_t));
	}
	if(!hdi) return -2;
	if(fs_newVirtualHDI(hdi,(uint16_t)blockCount,(uint32_t)blockSize)) return -1;
	if(fs_format(hdi))return -1;
	if(!fsh) fsh = malloc(sizeof(FileSystemHandle_t));
	if(!fsh) return 2;
	if(fs_openFileSystem(hdi,fsh)) return -1;
	currentDirectory = fsh->fsd.fsRoot;
	printf("Successfully formated the virtual drive.\n");
	return 0;
}

int Miniclosefs(){
	if(!hdi){
		printf("Can't delete a non-allocated virtual hard drive.\n");
		return -1;
	}
	if(fs_closeFileSystem(fsh)) return -1;
	if(fs_deleteVirtualHDI(hdi)) return -1;
	free(hdi);
	free(fsh);
	hdi = NULL;
	fsh = NULL;
	printf("Sucessfully deleted virtual drive.\n");
	return 0;
}

int Minisavefs(const char* name){
	if(avoidSegfaultWithFsh()) return -1;
	if(!fs_saveHDIToHardDrive(hdi, name)){
		printf("Successfully saved the virtual drive.\n");
		return 0;
	}
	printf("Failed to save the virtual drive.\n");
	return -1;
}

int Miniloadfs(const char* name){
	if(fsh){
		Miniclosefs();
	}
	hdi = malloc(sizeof(HardDriveInterface_t));
	if(!hdi) return 2;
	if(!fs_loadHDIFromHardDrive(hdi, name)){
		printf("Successfully opened the virtual drive file.\n");
	}else return -1;
	fsh = malloc(sizeof(FileSystemHandle_t));
	if(!fsh) return 2;
	printf("test3 %p,%p\n",hdi,fsh);
	if(fs_openFileSystem(hdi,fsh)) return -1;
	printf("test4\n");
	currentDirectory = fsh->fsd.fsRoot;
	printf("Successfully loaded the virtual drive.\n");
	return 0;
}

int Minidf(){
	uint32_t nBlockOccuiped;
	if(avoidSegfaultWithFsh()) return -1;
	if(fs_diskUsage(fsh,&nBlockOccuiped)){
		return -1;
	}
	printf("%d bytes free disk space.",(fsh->fsd.blockCount-nBlockOccuiped)*fsh->fsd.blockSize);
	printf("%d bytes total disk space.",fsh->fsd.blockCount*fsh->fsd.blockSize);
	printf("%d%% occupied total disk space.",(nBlockOccuiped*100)/fsh->fsd.blockCount);
	return 0;
}

/// Prints ls formated file info.
fs_ret_t fs_printFileInfols(inodeid_t address, const char * name){
	inode_t inode;
	char rights[10];
	int8_t i;
	fs_ret_t ret = fs_getInode(fsh,address,&inode);
	if(avoidSegfaultWithFsh()) return -1;
	if(ret){
		printf("Problem happend. Can't display inode %u info.\n",address);
		return ret;
	}
	if(inode.isFolder){
		printf("d");
	}else{
		printf("-");
	}
	for(i=0;i<3;++i){
		if(inode.rights&(4<<((2-i)*3))){
			rights[i*3+0] = 'r';
		}else{
			rights[i*3+0] = '-';
		}
		if(inode.rights&(2<<((2-i)*3))){
			rights[i*3+1] = 'w';
		}else{
			rights[i*3+1] = '-';
		}
		if(inode.rights&(1<<((2-i)*3))){
			rights[i*3+2] = 'x';
		}else{
			rights[i*3+2] = '-';
		}
	}
	rights[9] = '\0';
	printf("%s\t",rights);
	printf("%u\t",(unsigned int)inode.refCount);
	printf("nousr\t");
	printf("nogrp\t");
	/*if(inode.isFolder){
		printf("\t",(uint64_t)inode.fileSize);
	}else{*/
		printf("%llu\t",(long long unsigned)inode.fileSize);
	//}
	printf("%llu\t",(long long unsigned)inode.lastAccess);
	printf("%s\n",name);
	return ret;
}

int Minils(){
	int returnValue = 0;
	fs_ret_t ret = 0;
	inodeid_t address = 0;
	char *name = NULL;
	filepos_t cursor = 0;
	if(avoidSegfaultWithFsh()) return -1;
	name = malloc(257);
	if(!name) return 2;
	printf("duuugggooo\tHardR\tusr\tgrp\tsize\ttLast\tname\n");
	while(!ret){
		ret = fs_getNextFolderItem(fsh,currentDirectory,&cursor,name,&address);
		if(!ret){
			fs_printFileInfols(address, name);
		}else if(ret != ERR_OUT_OF_RANGE){
			ret = 1;
		}
	}
	free(name);
	return returnValue;
}

int Minimkdir(const char* name){
	int ret = 0;
	inodeid_t inDir = currentDirectory;
	inodeid_t iDontCareWhere;
	char *pathCpy;
	char head[257];
	if(avoidSegfaultWithFsh()) return -1;
	pathCpy = malloc(strlen(name)+1);
	if(!pathCpy) return -1;
	strcpy(pathCpy,name);
	if(fs_cutPathHead(pathCpy, head)){
		printf("Syntax error in given name.\n");
		ret = -1;
		goto cleanup;
	}
	if(!strcmp(head,"..")){// to avoid .. in "/"
		printf("srsly?\n");
		ret = 1;
		goto cleanup;
	}
	if(strcmp("",pathCpy) && fs_solvePath(fsh,inDir,pathCpy,NULL,&inDir)){
		printf("Could not reach the folder to create into.\n");
		ret = -1;
		goto cleanup;
	}
	if(!fs_getItemInFolderbyName(fsh,inDir,head,&iDontCareWhere)){
		printf("An item named \"%s\" already exist.\n",head);
		ret = -1;
		goto cleanup;
	}
	if(fs_createItemInFolder(fsh,inDir,NULL,head,true)){
		ret = -1;
		goto cleanup;
	}
	printf("Created folder \"%s\".\n",name);
	cleanup:
	free(pathCpy);
	return ret;
}

int Minicd(const char *path){
	inodeid_t tempId;
	if(avoidSegfaultWithFsh()) return -1;
	if(fs_solvePath(fsh,currentDirectory,path,NULL,&tempId)){
		printf("Could not reach directory.\n");
		return -1;
	}
	if(fs_assertInodeIsFolder(fsh,tempId)){
		printf("Cannot move to a file.\n");
	}
	currentDirectory = tempId;
	printf("Now in \"%s\".\n",path);
	return 0;
}

int Minirm(const char* itemName){
	int ret = 0;
	inodeid_t toDelete;
	inodeid_t inDir = currentDirectory;
	char *pathCpy;
	char head[257];
	if(avoidSegfaultWithFsh()) return -1;
	pathCpy = malloc(strlen(itemName)+1);
	if(!pathCpy) return 2;
	strcpy(pathCpy,itemName);
	if(fs_cutPathHead(pathCpy, head)){
		printf("Syntax error in given name.\n");
		ret = 1;
		goto cleanup;
	}
	if(strcmp("",pathCpy) && fs_solvePath(fsh,inDir,pathCpy,NULL,&inDir)){
		printf("Could not reach the folder to create into.\n");
	}
	if(!strcmp(head,".")||!strcmp(head,"..")){
		printf("srsly?\n");
		ret = 1;
		goto cleanup;
	}
	if(fs_getItemInFolderbyName(fsh,currentDirectory,head,&toDelete)){
		printf("Item not found in this folder : \"%s\". Could not delete it.\n",itemName);
		ret = 1;
		goto cleanup;
	}
	if(fs_removeInodeRef(fsh,toDelete)){
		ret = 1;
		goto cleanup;
	}
	if(fs_removeInodeFromFolder(fsh,currentDirectory,toDelete)){
		ret = 2;
		goto cleanup;
	}
	printf("Removed \"%s\" from folder.\n",itemName);
	cleanup:
	free(pathCpy);
	return ret;
}

int Miniopen(char *name,int flags){
	int ret = 0;
	inodeid_t inDir = currentDirectory;
	inodeid_t fileInodeId;
	filehandleid_t handleid;
	char *pathCpy;
	char head[257];
	if(avoidSegfaultWithFsh()) return -1;
	pathCpy = malloc(strlen(name)+1);
	if(!pathCpy) return -2;
	strcpy(pathCpy,name);
	if(fs_cutPathHead(pathCpy, head)){
		printf("Syntax error in given name.\n");
		ret = -1;
		goto cleanup;
	}
	if(!strcmp(head,"..")){// to avoid .. in "/"
		printf("srsly?\n");
		ret = 1;
		goto cleanup;
	}
	if(strcmp("",pathCpy) && fs_solvePath(fsh,inDir,pathCpy,NULL,&inDir)){
		printf("Could not reach the folder to create into.\n");
		ret = -1;
		goto cleanup;
	}
	// Checking if the file exist, and where.
	if(fs_getItemInFolderbyName(fsh,inDir,head,&fileInodeId)){
		if(FS_CREAT & flags){// Not existing, so creating it.
			if(fs_createItemInFolder(fsh,inDir,&fileInodeId,head,false)){
				ret = -1;
				printf("Failed to create file.\n");
				goto cleanup;
			}
		}else{
			ret = -1;
			printf("Item \"%s\" does not exist in this folder.\n",head);
			goto cleanup;
		}
	}else{
		if(!fs_assertInodeIsFolder(fsh,fileInodeId)){// If is a folder, problem.
			ret = -1;
			printf("Item \"%s\" is a folder.\n",head);
			goto cleanup;
		}
		/// \todo see if truncate is needed on creation.
	}
	if(fs_newFileHandle(fsh,fileInodeId,&handleid)){
		ret = -1;
		/// \todo Make different cases.
		printf("Item \"%s\" could not be opened.\n",head);
		goto cleanup;
	}
	ret = (filehandleid_t)handleid;
	if((FS_TRUNC&flags) && fs_deleteFileBlocks(fsh,&(fsh->openedFilesHandles[ret].inode))){
		ret = -1;
		printf("Failed while emptying \"%s\".\n",head);
		goto cleanup;
	}
	if(FS_APPEND&flags){
		fsh->openedFilesHandles[ret].cursor = fsh->openedFilesHandles[ret].inode.fileSize;
	}
	if(!(FS_WRONLY&flags)){
		fsh->openedFilesHandles[ret].isReadOnly = true;
	}
	if(!(FS_RDONLY&flags)){
		fsh->openedFilesHandles[ret].isWriteOnly = true;
	}
	printf("Opened \"%s\".\n",name);
	cleanup:
	free(pathCpy);
	return ret;
}

int Miniclose(int fildes){
	if(avoidSegfaultWithFsh()) return -1;
	if(fs_deleteFileHandle(fsh, (filehandleid_t)fildes)){
		return -1;
	}
	return 0;
}

int Miniseek(int fildes, char offset, int whence){
	filepos_t finalPos;
	if(avoidSegfaultWithFsh()) return -1;
	if(fs_seek(fsh,(filehandleid_t)fildes,offset,whence)){
		return -1;
	}
	if(fs_tell(fsh,(filehandleid_t)fildes,&finalPos)){
		return -1;
	}
	return (int)finalPos;
}

int Miniread(int fildes, void *buf, size_t nbyte){
	filepos_t startPos, endPos;
	fs_ret_t ret;
	if(avoidSegfaultWithFsh()) return -1;
	ret = fs_tell(fsh,(filehandleid_t)fildes,&startPos);
	if(ret){
		printf("ftell returned %d.\n",ret);
		return -1;
	}
	ret = fs_read(fsh,(filehandleid_t)fildes,(byte_t*)buf,nbyte);
	if(ret==ERR_PARAMETER_PROBLEM){
		printf("Problem with parameters.\n");
		return -1;
	}else if(ret){
		printf("function returned %d.\n",ret);
		return -1;
	}
	ret = fs_tell(fsh,(filehandleid_t)fildes,&endPos);
	if(ret){
		printf("ftell2 returned %d.\n",ret);
		return -1;
	}
	return (int)(endPos-startPos);
}

int Miniwrite(int fildes, const void *buf, size_t nbyte){
	fs_ret_t ret = fs_write(fsh,(filehandleid_t)fildes,(const byte_t*)buf,nbyte);
	if(avoidSegfaultWithFsh()) return -1;
	if(ret==ERR_PARAMETER_PROBLEM){
		printf("Problem with parameters.\n");
		return -1;
	}else if(ret){
		printf("function returned %d.\n",ret);
		return -1;
	}
	return nbyte;
}

int Minicp(const char *from,const char *to){
	inodeid_t fromId, toId;
	int ret = 0;
	char *toPath;
	char toName[257];
	if(avoidSegfaultWithFsh()) return -1;
	toPath = malloc(strlen(to)+1);
	if(!toPath) return -1;
	if(fs_solvePath(fsh,currentDirectory,from,NULL,&fromId)){
		printf("Failed to solve the path to the item to copy.\n");
		ret = -1;
		goto cleanup;
	}
	strcpy(toPath,to);
	if(fs_cutPathHead(toPath,toName)){
		printf("Syntax error in the destination path.\n");
		ret = -1;
		goto cleanup;
	}
	if(fs_solvePath(fsh,currentDirectory,toPath,NULL,&toId)){
		printf("Failed to solve the path to the copy destination.\n");
		ret = -1;
		goto cleanup;
	}
	if(fs_copyItemRecursive(fsh,fromId,toId,toName,NULL)){
		printf("Failed to copy.");
		ret = -1;
		goto cleanup;
	}
	cleanup:
	free(toPath);
	return ret;
}

int Miniputc(int fd,char c){
	return (Miniwrite(fd,&c,1)==1)?0:-1;
}

int Minigetc(int fd){
	unsigned char c;
	if(Miniread(fd,&c,1)!=1){
		return -1;
	}
	return (int)c;
}
